package ua.kratik.seabattle.game;

import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import org.apache.log4j.NDC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ua.kratik.seabattle.domain.BattleRules;
import ua.kratik.seabattle.domain.User;
import ua.kratik.seabattle.game.enums.Direction;
import ua.kratik.seabattle.game.enums.ShootResult;
import ua.kratik.seabattle.game.enums.CellState;

/**
 * Workflow: <br>
 * 
 * @author Andrii_Kratik
 * 
 */
public class Admiral implements Enemy {

	private static Logger logger = LoggerFactory.getLogger(Admiral.class);

	private AdmiralsMap myMap;

	private EnemysMap enemysMap;

	private User user;

	private Enemy enemy;

	private AdmiralsFleet myFleet;

	private AdmiralsFleet enemyFleet;

	private BattleRules rules;

	/**
	 * Indicates whether the game is in progress or not
	 */
	private boolean gameInProgress;

	private boolean gameFinished;

	private boolean winner;

	/**
	 * Indicates whether the admiral can shoot or not. First admiral who clicked
	 * 'Play' can shoot first.
	 */
	private boolean canShoot;

	public Admiral(User user) {
		this.user = user;
		gameFinished = false;
		gameInProgress = false;
		canShoot = false;
		winner = false;
	}

	/**
	 * Means that you define battle rules (size of your see and see of yours
	 * opponent, number and size of ships). The rules are the same for both
	 * players.
	 * 
	 * @param rules
	 *            - battle rules to be set
	 */
	public void setBattleRules(BattleRules rules) {
		if (!gameInProgress) {
			this.rules = rules;
			winner = false;
			gameFinished = false;
		} else {
			logger.warn("Battle rules cannot be set because the game is in progress");
		}

		// initialization of mySeaMap size, fleet (and ships in fleet)
		logger.debug("setBattleRules: creating myMap...");
		myMap = new AdmiralsMap(rules.getSeaSizeX(), rules.getSeaSizeY());
		myMap.cleanMap();

		logger.debug("setBattleRules: myMap created, creating enemysMap...");
		enemysMap = new EnemysMap(rules.getSeaSizeX(), rules.getSeaSizeY());
		enemysMap.cleanMap();

		logger.debug("setBattleRules: enemysMap created, creating myFleet..."
				+ rules.getFleet().toString());
		myFleet = new AdmiralsFleet(rules.getFleet());

		logger.debug("setBattleRules: myFleet created, creating enemyFleet...");
		enemyFleet = new AdmiralsFleet(rules.getFleet());

		logger.debug("setBattleRules: enemyFleet created.");
		logger.debug("setBattleRules: done.");
	}

	/**
	 * Here is implemented our reaction on shooting at this Admiral instance
	 */
	public ShootResult shoot(Coordinates c) throws IllegalArgumentException,
			NullPointerException {
		// NDC
		NDC.push(this.user.getEmail());
		logger.debug("shoot method was called");

		CellState state = myMap.getCellState(c);
		ShootResult result = ShootResult.SHOOT_PROHIBITED;
		if (!gameFinished) {
			// if enemy can shoot
			if (!canShoot) {
				switch (state) {
				case CONTAIN_SHIP:
					// find ship on this cell
					Ship ship = myMap.findShipOnCell(c);
					// if number of alive cells is 1 then return
					// SHOT_AND_DESTROYED
					if (ship.getAlliveCellsCount(myMap) == 1) {// the last one
						myMap.fillCells(CellState.SHOT_AND_DESTROYED,
								ship.getPositionMap());
						result = ShootResult.DESTROYED;
						if (myFleet.decreaseAliveShipsCount() == 0) {
							gameFinished = true;
							winner = false;
						}
					} else {
						myMap.fillCells(CellState.SHOT_AND_HIT, c);
						result = ShootResult.HIT;
					}
					break;
				case EMPTY:
				case CANNOT_CONTAIN_SHIP:
					myMap.fillCells(CellState.SHOT_AND_MISSED, c);
					result = ShootResult.MISSED;
					canShoot = true;
					break;
				default:
					logger.warn("CellState '" + state.name()
							+ "' is not prossed");
					result = ShootResult.SHOOT_PROHIBITED;
				}
			} else {
				result = ShootResult.SHOOT_PROHIBITED;
			}
		}
		NDC.pop();
		return result;
	}

	public void updateEnemyMap(Coordinates cell, ShootResult shootResult) {
		NDC.push(this.user.getEmail());
		try {
			canShoot = enemysMap.drawSootResult(cell, shootResult);
			if (shootResult == ShootResult.DESTROYED) {
				if (enemyFleet.decreaseAliveShipsCount() <= 0) {
					canShoot = false;
					gameFinished = true;
					winner = true;
				}
			}
		} finally {
			NDC.pop();
		}
	}

	public SeaMap autoLocateShips() {
		List<Ship> ships = myFleet.getShips();
		// Sorts the specified list into descending order according to the size
		Collections.sort(ships);
		Coordinates[] pos;
		Ship ship;
		myMap.cleanMap();
		Iterator<Ship> it = ships.iterator();
		while (it.hasNext()) {
			ship = it.next();
			pos = myMap.getFreeRandomLocation(ship.getSize());
			logger.debug("'" + ship.getShipBean().getName()
					+ "' ship coordinates: " + Arrays.toString(pos));
			locateShipInRandomDirection(ship, pos);
		}
		return myMap;
	}

	private void locateShipInRandomDirection(Ship ship, Coordinates[] pos) {
		if (pos.length == 1) {
			locateShip(ship, pos[0], Direction.getRandomDirection());
		} else {
			Coordinates c1 = pos[0];
			Coordinates c2 = pos[pos.length - 1];
			// if horizontal
			if ((c1.getX() - c2.getX()) != 0) {
				if (c1.getX() < c2.getX()) {
					locateShip(ship, c1, Direction.EAST);
				} else {
					locateShip(ship, c1, Direction.WEST);
				}
			} else { // if vertical
				if (c1.getY() < c2.getY()) {
					locateShip(ship, c1, Direction.SOUTH);
				} else {
					locateShip(ship, c1, Direction.NORTH);
				}
			}
		}
	}

	public Coordinates[] locateShip(Ship ship, Coordinates sternPosition,
			Direction direction) {

		Coordinates[] c = ship.getPossibleLocation(sternPosition, direction);
		if (isLocationFree(c)) {
			ship.goTo(c);
			myMap.drawShip(ship);
			if (logger.isTraceEnabled()) {
				StringBuilder b = new StringBuilder();
				b.append("Ship '");
				b.append(ship.getShipBean().getName());
				b.append("' size=[");
				b.append(ship.getSize());
				b.append("] went to stern=[");
				b.append(sternPosition.toString());
				b.append("] ");
				b.append(direction);
				logger.trace(b.toString());
			}
		}
		return c;
	}

	/**
	 * Always returns true!!!
	 * 
	 * @param c
	 * @return
	 */
	private boolean isLocationFree(Coordinates[] c) {
		return true;
	}

	public SeaMap getMySeaMap() {
		return myMap;
	}

	public User getUser() {
		return user;
	}

	public BattleRules getRules() {
		return this.rules;
	}

	public boolean areRulesEqual(BattleRules rules) {
		return this.rules.getId() == rules.getId();
	}

	public void setEnemy(Enemy enemy) {
		this.canShoot = false;
		this.enemy = enemy;
		this.gameInProgress = true;
		enemy.challengeAccepted((Enemy) this);
	}

	public void challengeAccepted(Enemy enemy) {
		this.canShoot = true;
		this.gameInProgress = true;
		this.enemy = enemy;
	}

	public SeaMap getEnemysMap() {
		return enemysMap;
	}

	public AdmiralsMap getMyMap() {
		return myMap;
	}

	public Enemy getEnemy() {
		return enemy;
	}

	public AdmiralsFleet getEnemyFleet() {
		return enemyFleet;
	}

	public boolean isGameInProgress() {
		return gameInProgress;
	}

	public String getEmail() {
		return user.getEmail();
	}

}
